/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.animdemo.game;

import java.util.Comparator;

import com.example.animdemo.FixedSizeArray;
import com.example.animdemo.TObjectPool;
import com.example.animdemo.base.BaseObject;
import com.example.animdemo.base.DebugLog;
import com.example.animdemo.render.ContextParameters;

/** A class for generating game objects at runtime.
 * This should really be replaced with something that is data-driven, but it is hard to do data
 * parsing quickly at runtime.  For the moment this class is full of large functions that just
 * patch pointers between objects, but in the future those functions should either be 
 * a) generated from data at compile time, or b) described by data at runtime.
 */
public class GameObjectFactory extends BaseObject {
    private final static int MAX_GAME_OBJECTS = 384;
    private final static ComponentPoolComparator sComponentPoolComparator = new ComponentPoolComparator();
    private FixedSizeArray<FixedSizeArray<BaseObject>> mStaticData;
    private FixedSizeArray<GameComponentPool> mComponentPools;
    private GameComponentPool mPoolSearchDummy;
    private GameObjectPool mGameObjectPool;
    
    private float mTightActivationRadius;
    private float mNormalActivationRadius;
    private float mWideActivationRadius;
    private float mAlwaysActive;
    
    private final static String sRedButtonChannel = "RED BUTTON";
    private final static String sBlueButtonChannel = "BLUE BUTTON";
    private final static String sGreenButtonChannel = "GREEN BUTTON";
    private final static String sSurprisedNPCChannel = "SURPRISED";
    
    
    // A list of game objects that can be spawned at runtime.  Note that the indicies of these
    // objects must match the order of the object tileset in the level editor in order for the 
    // level content to make sense.
    public enum GameObjectType {
        INVALID(-1),
//        
//        PLAYER (0),
//        
//        // Collectables
//        COIN (1),
//        RUBY (2),
//        DIARY (3),
//        
//        // Characters
//        WANDA (10),
//        KYLE (11),
//        KYLE_DEAD (12),
//        ANDOU_DEAD (13),
//        KABOCHA (26),
//        ROKUDOU_TERMINAL (27),
//        KABOCHA_TERMINAL (28),
//        EVIL_KABOCHA (29),
//        ROKUDOU (30),
//        
//        // AI
//        BROBOT (16),
//        SNAILBOMB (17),
//        SHADOWSLIME (18),
//        MUDMAN (19),
//        SKELETON (20),
//        KARAGUIN (21),
//        PINK_NAMAZU (22),
//        TURRET (23),
//        TURRET_LEFT (24),
//        BAT (6),
//        STING(7),
//        ONION(8),
//        
//        // Objects
//        DOOR_RED (32),
//        DOOR_BLUE (33),
//        DOOR_GREEN (34),
//        BUTTON_RED (35),
//        BUTTON_BLUE (36),
//        BUTTON_GREEN (37),
//        CANNON (38),
//        BROBOT_SPAWNER (39),
//        BROBOT_SPAWNER_LEFT (40),
//        BREAKABLE_BLOCK(41),
//        THE_SOURCE(42),
//        HINT_SIGN(43),
//        
//        // Effects
//        DUST(48),
//        EXPLOSION_SMALL(49),
//        EXPLOSION_LARGE(50),
//        EXPLOSION_GIANT(51),
//        
//        
//        // Special Spawnable
//        DOOR_RED_NONBLOCKING (52),
//        DOOR_BLUE_NONBLOCKING (53),
//        DOOR_GREEN_NONBLOCKING (54),
//        
//        GHOST_NPC(55),
//        
//        CAMERA_BIAS(56),
//        
//        FRAMERATE_WATCHER(57),
//        INFINITE_SPAWNER(58),
//        CRUSHER_ANDOU(59),
//        
//        
//        // Projectiles
//        ENERGY_BALL(68),
//        CANNON_BALL(65),
//        TURRET_BULLET(66),
//        BROBOT_BULLET(67),
//        BREAKABLE_BLOCK_PIECE(68),
//        BREAKABLE_BLOCK_PIECE_SPAWNER(69),
//        WANDA_SHOT(70),
//        
//        // Special Objects -- Not spawnable normally
//        SMOKE_BIG(-1),
//        SMOKE_SMALL(-1),
//        
//        CRUSH_FLASH(-1),
//        FLASH(-1),
//        
//        PLAYER_JETS(-1),
//        PLAYER_SPARKS(-1),
//        PLAYER_GLOW(-1),
//        ENEMY_SPARKS(-1),
//        GHOST(-1),
//        SMOKE_POOF(-1),
//        GEM_EFFECT(-1),
//        GEM_EFFECT_SPAWNER(-1),
        
        
        // End
        OBJECT_COUNT(-1);
        
        private final int mIndex;
        GameObjectType(int index) {
            this.mIndex = index;
        }
        
        public int index() {
            return mIndex;
        }
        
        // TODO: Is there any better way to do this?
        public static GameObjectType indexToType(int index) {
            final GameObjectType[] valuesArray = values();
            GameObjectType foundType = INVALID;
            for (int x = 0; x < valuesArray.length; x++) {
                GameObjectType type = valuesArray[x];
                if (type.mIndex == index) {
                    foundType = type;
                    break;
                }
            }
            return foundType;
        }
        
    }
    
    public GameObjectFactory() {
        super();
        
        mGameObjectPool = new GameObjectPool(MAX_GAME_OBJECTS);
        
        final int objectTypeCount = GameObjectType.OBJECT_COUNT.ordinal();
        mStaticData = new FixedSizeArray<FixedSizeArray<BaseObject>>(objectTypeCount);
        
        for (int x = 0; x < objectTypeCount; x++) {
            mStaticData.add(null);
        }
        
        final ContextParameters context = sSystemRegistry.contextParameters;
        final float halfHeight2 = (context.gameHeight * 0.5f) * (context.gameHeight * 0.5f);
        final float halfWidth2 = (context.gameWidth * 0.5f) * (context.gameWidth * 0.5f);
        final float screenSizeRadius = (float)Math.sqrt(halfHeight2 + halfWidth2);
        mTightActivationRadius = screenSizeRadius + 128.0f;
        mNormalActivationRadius = screenSizeRadius * 1.25f;
        mWideActivationRadius = screenSizeRadius * 2.0f;
        mAlwaysActive = -1.0f;
       
        // TODO: I wish there was a way to do this automatically, but the ClassLoader doesn't seem
        // to provide access to the currently loaded class list.  There's some discussion of walking
        // the actual class file objects and using forName() to instantiate them, but that sounds
        // really heavy-weight.  For now I'll rely on (sucky) manual enumeration.
        class ComponentClass {
            public Class<?> type;
            public int poolSize;
            public ComponentClass(Class<?> classType, int size) {
                type = classType;
                poolSize = size;
            }
        }
        ComponentClass[] componentTypes = {
//                new ComponentClass(AnimationComponent.class, 1),
//                new ComponentClass(AttackAtDistanceComponent.class, 16),
//                new ComponentClass(BackgroundCollisionComponent.class, 192),
//                new ComponentClass(ButtonAnimationComponent.class, 32),
//                new ComponentClass(CameraBiasComponent.class, 8),
//                new ComponentClass(ChangeComponentsComponent.class, 256),
//                new ComponentClass(CrusherAndouComponent.class, 1),
//                new ComponentClass(DoorAnimationComponent.class, 256),  //!
//                new ComponentClass(DynamicCollisionComponent.class, 256),
//                new ComponentClass(EnemyAnimationComponent.class, 256),
//                new ComponentClass(FadeDrawableComponent.class, 32),
//                new ComponentClass(FixedAnimationComponent.class, 8),
//                new ComponentClass(FrameRateWatcherComponent.class, 1),
//                new ComponentClass(GenericAnimationComponent.class, 32),
//                new ComponentClass(GhostComponent.class, 256),
//                new ComponentClass(GravityComponent.class, 128),
//                new ComponentClass(HitPlayerComponent.class, 256),
//                new ComponentClass(HitReactionComponent.class, 256),
//                new ComponentClass(InventoryComponent.class, 128),
//                new ComponentClass(LauncherComponent.class, 16),
//                new ComponentClass(LaunchProjectileComponent.class, 128),
//                new ComponentClass(LifetimeComponent.class, 384),
//                new ComponentClass(MotionBlurComponent.class, 1),
//                new ComponentClass(MovementComponent.class, 128),
//                new ComponentClass(NPCAnimationComponent.class, 8),
//                new ComponentClass(NPCComponent.class, 8),
//                new ComponentClass(OrbitalMagnetComponent.class, 1),
//                new ComponentClass(PatrolComponent.class, 256),
//                new ComponentClass(PhysicsComponent.class, 8),
//                new ComponentClass(PlayerComponent.class, 1),
//                new ComponentClass(PlaySingleSoundComponent.class, 128),
//                new ComponentClass(PopOutComponent.class, 32),
//                new ComponentClass(RenderComponent.class, 384),
//                new ComponentClass(ScrollerComponent.class, 8),
//                new ComponentClass(SelectDialogComponent.class, 8),
//                new ComponentClass(SimpleCollisionComponent.class, 32),
//                new ComponentClass(SimplePhysicsComponent.class, 256),
//                new ComponentClass(SleeperComponent.class, 32),
//                new ComponentClass(SolidSurfaceComponent.class, 16),
//                new ComponentClass(SpriteComponent.class, 384),
//                new ComponentClass(TheSourceComponent.class, 1),
                
        };
        
        mComponentPools = new FixedSizeArray<GameComponentPool>(componentTypes.length, sComponentPoolComparator);
        for (int x = 0; x < componentTypes.length; x++) {
            ComponentClass component = componentTypes[x];
            mComponentPools.add(new GameComponentPool(component.type, component.poolSize));
        }
        mComponentPools.sort(true);
        
        mPoolSearchDummy = new GameComponentPool(Object.class, 1);
        
    }
    
    @Override
    public void reset() {
        
    }
    
    protected GameComponentPool getComponentPool(Class<?> componentType) {
        GameComponentPool pool = null;
        mPoolSearchDummy.objectClass = componentType;
        final int index = mComponentPools.find(mPoolSearchDummy, false);
        if (index != -1) {
            pool = mComponentPools.get(index);
        }
        return pool;
    }
    
    protected GameComponent allocateComponent(Class<?> componentType) {
        GameComponentPool pool = getComponentPool(componentType);
        assert pool != null;
        GameComponent component = null;
        if (pool != null) {
            component = pool.allocate();
        }
        return component;
    }
    
    protected void releaseComponent(GameComponent component) {
        GameComponentPool pool = getComponentPool(component.getClass());
        assert pool != null;
        if (pool != null) {
            component.reset();
            component.shared = false;
            pool.release(component);
        }
    }
    
    protected boolean componentAvailable(Class<?> componentType, int count) {
    	boolean canAllocate = false;
        GameComponentPool pool = getComponentPool(componentType);
        assert pool != null;
        if (pool != null) {
        	canAllocate = pool.getAllocatedCount() + count < pool.getSize();
        }
        return canAllocate;
    }
    
    public void preloadEffects() {
        // These textures appear in every level, so they are long-term.
        
    }
    
    public void destroy(GameObject object) {
        object.commitUpdates();
        final int componentCount = object.getCount();
        for (int x = 0; x < componentCount; x++) {
            GameComponent component = (GameComponent)object.get(x);
            if (!component.shared) {
                releaseComponent(component);
            }
        }
        object.removeAll();
        object.commitUpdates();
        mGameObjectPool.release(object);
    }
    
    public GameObject spawn(GameObjectType type, float x, float y, boolean horzFlip) {
        GameObject newObject = null;
        switch(type) {
            
        }
        
        return newObject;
    }
    
    private FixedSizeArray<BaseObject> getStaticData(GameObjectType type) {
        return mStaticData.get(type.ordinal());
    }
    
    private void setStaticData(GameObjectType type, FixedSizeArray<BaseObject> data) {
        int index = type.ordinal();
        assert mStaticData.get(index) == null;
        
        final int staticDataCount = data.getCount();

        for (int x = 0; x < staticDataCount; x++) {
            BaseObject entry = data.get(x);
            if (entry instanceof GameComponent) {
                ((GameComponent) entry).shared = true;
            }
        }
        
        mStaticData.set(index, data);
    }
    
//    private void addStaticData(GameObjectType type, GameObject object, SpriteComponent sprite) {
//        FixedSizeArray<BaseObject> staticData = getStaticData(type);
//        assert staticData != null;
//
//        if (staticData != null) {
//            final int staticDataCount = staticData.getCount();
//            
//            for (int x = 0; x < staticDataCount; x++) {
//                BaseObject entry = staticData.get(x);
//                if (entry instanceof GameComponent && object != null) {
//                    object.add((GameComponent)entry);
//                } else if (entry instanceof SpriteAnimation && sprite != null) {
//                    sprite.addAnimation((SpriteAnimation)entry);
//                }
//            }
//        }
//    }
    
    public void clearStaticData() {
        final int typeCount = mStaticData.getCount();
        for (int x = 0; x < typeCount; x++) {
            FixedSizeArray<BaseObject> staticData = mStaticData.get(x);
            if (staticData != null) {
                final int count = staticData.getCount();
                for (int y = 0; y < count; y++) {
                    BaseObject entry = staticData.get(y);
                    if (entry != null) {
                        if (entry instanceof GameComponent) {
                            releaseComponent((GameComponent)entry);
                        } 
                    }
                }
                staticData.clear();
                mStaticData.set(x, null);
            }
        }
    }
    
    public void sanityCheckPools() {
        final int outstandingObjects = mGameObjectPool.getAllocatedCount();
        if (outstandingObjects != 0) {
            DebugLog.d("Sanity Check", "Outstanding game object allocations! (" 
                    + outstandingObjects + ")");
            assert false;
        }
        
        final int componentPoolCount = mComponentPools.getCount();
        for (int x = 0; x < componentPoolCount; x++) {
            final int outstandingComponents = mComponentPools.get(x).getAllocatedCount();
            
            if (outstandingComponents != 0) {
                DebugLog.d("Sanity Check", "Outstanding " 
                        + mComponentPools.get(x).objectClass.getSimpleName()
                        + " allocations! (" + outstandingComponents + ")");
                //assert false;
            }
        }
    }
    
    
    
    
    /** Comparator for game objects objects. */
    private final static class ComponentPoolComparator implements Comparator<GameComponentPool> {
        public int compare(final GameComponentPool object1, final GameComponentPool object2) {
            int result = 0;
            if (object1 == null && object2 != null) {
                result = 1;
            } else if (object1 != null && object2 == null) {
                result = -1;
            } else if (object1 != null && object2 != null) {
                result = object1.objectClass.hashCode() - object2.objectClass.hashCode();
            }
            return result;
        }
    }
    
    public class GameObjectPool extends TObjectPool<GameObject> {

        public GameObjectPool() {
            super();
        }
        
        public GameObjectPool(int size) {
            super(size);
        }
        
        @Override
        protected void fill() {
            for (int x = 0; x < getSize(); x++) {
                getAvailable().add(new GameObject());
            }
        }

        @Override
        public void release(Object entry) {
            ((GameObject)entry).reset();
            super.release(entry);
        }

    }
}

